#include "multiset.h"

#include <bool.h>
#include <number.h>
#include <typefactory.h>

#include <QtCore/QStringList>
#include <QtCore/QVariant>

using namespace core;
using namespace wfnet;

const TypeName Multiset::NAME = Type::demangledName(typeid(Multiset).name());
const ValueKeywordMask Multiset::KEYWORD_MASK =
    "\\{(\\d+\\*%1(@\\d+){0,1}(\\s\\+\\s){0,1})+\\}";

Multiset::Multiset() : IMultiset(), Type(),
    m_colSetName(),
    m_values(),
    m_name(NAME)
{
    fillOperations();
}

Multiset::Multiset(const ColorSetName& colSetName) : IMultiset(), Type(),
    m_colSetName(colSetName),
    m_values(),
    m_name(QString("%1_%2").arg(NAME).arg(m_colSetName))
{
    fillOperations();
}

Multiset::Multiset(const ColorSetName& colSetName,
    const TypeValue& value) : IMultiset(), Type(),
        m_colSetName(colSetName),
        m_values(),
        m_name(QString("%1_%2").arg(NAME).arg(m_colSetName))
{
    fillOperations();

    parseValue(value);
}

Multiset::Multiset(const Multiset& other) : IMultiset(), Type(),
    m_colSetName(other.m_colSetName),
    m_values(other.m_values),
    m_name(other.m_name)
{
    fillOperations();
}

Multiset::Multiset(const Multiset& other, const TypeValue& value) :
    IMultiset(), Type(),
    m_colSetName(other.m_colSetName),
    m_values(),
    m_name(other.m_name)
{
    fillOperations();

    parseValue(value);
}

Multiset::~Multiset()
{

}

bool Multiset::isTimed() const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    return false;
}

QString Multiset::name() const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    return m_name;
}

bool Multiset::matches(const TypeName& name) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    const Type* type =
        TypeFactory::getInstance()->getTypeInstance(m_colSetName);

    Q_ASSERT(type != 0);

    return type->name() == name || m_name == name || NAME == name;
}

bool Multiset::matches(const Type& type) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    const Multiset* other = dynamic_cast<const Multiset* >(&type);
    const ColorSet* otherAsColorSet = dynamic_cast<const ColorSet* >(&type);
    return other != 0 ? m_name == other->m_name :
        otherAsColorSet != 0 ? otherAsColorSet->matches(m_colSetName) :
            false;
}

bool Multiset::canConvert(const TypeValue& value) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    bool canConvert = true;

    const Type* type =
        TypeFactory::getInstance()->getTypeInstance(m_colSetName);

    Q_ASSERT(type != 0);

    QRegExp regex(keywordMask().arg(type->keywordMask()));
    bool canParse = regex.exactMatch(value);

    if (canParse)
    {
        if (!value.contains("@"))
        {
            regex.setPattern("(\\{|\\})");
            QString strippedValue = value;
            strippedValue = strippedValue.remove(regex);

            regex.setPattern("\\s\\+\\s");
            const QStringList values = strippedValue.split(regex,
                QString::SkipEmptyParts);

            foreach (const QString& _value, values)
            {
                const QStringList _valueSplit = _value.split("*",
                    QString::SkipEmptyParts);

                if (!type->canConvert(_valueSplit[1]))
                {
                    canConvert = false;
                    break;
                }
            }
        }
    }
    else
    {
        canConvert = false;
    }

    return canConvert;
}

ValueKeywordMask Multiset::keywordMask() const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    return KEYWORD_MASK;
}

bool Multiset::hasIdentity() const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    return true;
}

void Multiset::setIdentity(const Type& identity)
{
    const Multiset* other = dynamic_cast<const Multiset* >(&identity);

    if (other != 0 && m_colSetName.isEmpty())
    {
        m_colSetName = other->m_colSetName;
        m_name = constructName(m_colSetName);
    }
}

Type* Multiset::copy() const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    Multiset* newInstance = new Multiset(*this);
    return newInstance;
}

TypeValue Multiset::toValue() const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    TypeValue value = "{";

    foreach (const ColorSet& color, m_values.keys())
    {
        const uint coefficient = m_values[color];

        value.append(QString("%1*%2 + ").arg(coefficient).arg(
            color.getValue().toString()));
    }

    value.remove(value.length() - 3, 3);
    value.append("}");

    return value;
}

bool Multiset::isEmpty() const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    return m_values.isEmpty();
}

uint Multiset::size() const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    return m_values.size();
}

bool Multiset::contains(const ColorSet& value) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    return m_values.contains(value);
}

uint Multiset::coefficient(const ColorSet& value) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    return m_values.contains(value) ? m_values[value] : 0;
}

void Multiset::insert(const ColorSet& value)
{
    Q_ASSERT(!m_colSetName.isEmpty());

    if (m_colSetName == value.name() && !value.isTimed())
    {
        if (m_values.contains(value))
        {
            ++m_values[value];
        }
        else
        {
            m_values[value] = 1;
        }
    }
    else
    {
        Q_ASSERT(false);
    }
}

void Multiset::remove(const ColorSet& value)
{
    Q_ASSERT(!m_colSetName.isEmpty());

    if (m_colSetName == value.name() && !value.isTimed())
    {
        if (m_values.contains(value))
        {
            uint& count = m_values[value];
            if (count == 1)
            {
                m_values.remove(value);
            }
            else
            {
                --count;
            }
        }
    }
    else
    {
        Q_ASSERT(false);
    }
}

void Multiset::clear()
{
    Q_ASSERT(!m_colSetName.isEmpty());

    m_values.clear();
}

ColorSet Multiset::at(uint index) const
{
    Q_ASSERT(!m_colSetName.isEmpty() && index < (uint)m_values.size());

    return m_values.keys().at(index);
}

Multiset Multiset::intersect(const Multiset& other) const
{
    Multiset mt(m_colSetName);

    const QSet<ColorSet> intersectedKeys = m_values.keys().toSet().intersect(
        other.m_values.keys().toSet());

    foreach (const ColorSet& color, intersectedKeys)
    {
        const uint maxIntrCoefficient =
            m_values[color] > other.m_values[color] ?
                other.m_values[color] : m_values[color];

        mt.m_values[color] = maxIntrCoefficient;
    }

    return mt;
}

Multiset Multiset::operator+(const Multiset& other) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    Multiset ret = *this;

    if (m_colSetName == other.m_colSetName)
    {
        ConstPrivateIterator itOther = other.m_values.constBegin();

        while (itOther != other.m_values.constEnd())
        {
            if (ret.m_values.find(itOther.key()) != ret.m_values.constEnd())
            {
                ret.m_values[itOther.key()] += itOther.value();
            }
            else
            {
                ret.m_values[itOther.key()] = 0;
                ret.m_values[itOther.key()] += itOther.value();
            }
            ++itOther;
        }
    }
    else
    {
        Q_ASSERT(false);
    }

    return ret;
}

Multiset Multiset::operator-(const Multiset& other) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    Multiset ret(this->m_colSetName);

    if (m_colSetName == other.m_colSetName)
    {
        ConstPrivateIterator itOther = other.m_values.constBegin();

        if (other <= *this)
        {
            ret = *this;

            while (itOther != other.m_values.constEnd())
            {
                ret.m_values[itOther.key()] -= itOther.value();
                if (ret.m_values[itOther.key()] == 0)
                {
                    ret.m_values.remove(itOther.key());
                }

                ++itOther;
            }
        }
    }
    else
    {
        Q_ASSERT(false);
    }

    return ret;
}

Multiset Multiset::operator*(uint scalar) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    Multiset ret = *this;

    PrivateIterator itRet = ret.m_values.begin();

    while(itRet != ret.m_values.end())
    {
        itRet.value() *= scalar;
        ++itRet;
    }

    return ret;
}

bool Multiset::operator<=(const Multiset& other) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    bool ret = true;

    if (m_colSetName == other.m_colSetName)
    {
        ConstPrivateIterator itThis = m_values.constBegin();

        while (itThis != m_values.constEnd())
        {
            if (itThis.value() > other.coefficient(itThis.key()))
            {
                ret = false;
                break;
            }
            ++itThis;
        }
    }
    else
    {
        Q_ASSERT(false);
    }

    return ret;
}

TypeName Multiset::typeOf() const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    return m_colSetName;
}

Multiset::ConstIterator Multiset::constBegin() const
{
    return m_values.keys().toSet().constBegin();
}

Multiset::ConstIterator Multiset::constEnd() const
{
    return m_values.keys().toSet().constEnd();
}

TypeName Multiset::constructName(const ColorSetName& name)
{
    return QString("%1_%2").arg(NAME).arg(name);
}

void Multiset::parseValue(const TypeValue& value)
{
    if (canConvert(value))
    {
        const ColorSet* type = dynamic_cast<const ColorSet* >(
            TypeFactory::getInstance()->getTypeInstance(m_colSetName));

        Q_ASSERT(type != 0);

        QRegExp regex("(\\{|\\})");
        QString strippedValue = value;
        strippedValue = strippedValue.remove(regex);

        regex.setPattern("\\s\\+\\s");
        const QStringList values = strippedValue.split(regex,
            QString::SkipEmptyParts);

        foreach (const QString& _value, values)
        {
            const QStringList _valueSplit = _value.split("*",
                QString::SkipEmptyParts);

            const int coefficient = _valueSplit[0].toInt();

            for (int i = 0; i < coefficient; ++i)
            {
                ColorSet color = *type;
                color.setValue(QVariant(_valueSplit[1]));
                insert(color);
            }
        }
    }
}

void Multiset::fillOperations()
{
    //m_operations.insert(OPERATION_EQUALS, OP(Bool::NAME));
    //m_operations.insert(OPERATION_NEQUALS, OP(Bool::NAME));
    m_operations.insert(OPERATION_LEQUALTO, OP(Bool::NAME));

    m_operations.insert(OPERATION_ADDITION, OP(NAME));
    m_operations.insert(OPERATION_SUBSTRACTION, OP(NAME));
    m_operations.insert(OPERATION_MULTIPLICATION, OP2(NAME, Number::NAME));
}
